package com.xpec.c4.game.character;

import java.io.Serializable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.xpec.c4.game.utility.GUID;
import com.xpec.c4.message.MessageTypes;
import com.xpec.c4.service.message.MessageAction;
import com.xpec.c4.service.message.NetMessage;
import com.xpec.c4.service.module.PropertyKey;
import com.xpec.c4.service.module.ServerProperties;

/**
 * 玩家定期統計資訊, 不存DB, 每次登入重建<br>
 * 
 * 處理心跳消息、加速器檢查
 * 
 * @author wuweijun
 */
public class PeriodSyncStatisInfo implements Serializable{
	private static final long serialVersionUID = -4815151333626822820L;
	
	private static final Log log = LogFactory.getLog(PeriodSyncStatisInfo.class);
	/**
	 * 如果5分鐘內都沒有發送period, 則踢掉, millis sec
	 */
	private final static int SYNC_TIMEOUT_PERIOD = ServerProperties.getInt(PropertyKey.period_sync_threshold, 10 *60 ) * 1000;
	/**
	 * 是否 啟動 frequency check
	 */
	private final static boolean ENABLE_FREQUENCY_CHECK = ServerProperties.getBoolean(PropertyKey.enable_frequency_detect, true);
	/**
	 * 是否 啟動 processTime check
	 */
	private final static boolean ENABLE_PROCESSTIME_CHECK = ServerProperties.getBoolean(PropertyKey.enable_precesstime_detect, true);
	/**
	 * 累積統計頻率的時間
	 */
	private final static long MAX_STATIC_PERIOD = ServerProperties.getLong(PropertyKey.frequency_detect_max_period_sec, 3000) *1000;
	/**
	 * 最小開始踢人等待統計時間(至少統計時間 > 此職後, 才能把不正常的給踢走)
	 */
	private final static long MIN_BEGIN_KICK_STATIC_PERIOD  = ServerProperties.getInt(PropertyKey.frequency_detect_wait_static_time, 60) *1000;
	/**
	 * 踢人頻率誤差容忍百分比門檻 (0-100)
	 */
	private final static int KICK_FRQUENCY_INACCURACY  = ServerProperties.getInt(PropertyKey.frequencye_detect_inaccuracy_threshold, 50);
	/**
	 * 標準的週期值, millis sec 
	 */
	private final static int SYNC_CYCLE_STANDER  = ServerProperties.getInt(PropertyKey.period_sync_cycle, 5) *1000;
	/**
	 * 踢人時差誤差門檻 ( + 此值 則踢掉 , 頻率誤差值) , client-> server計時器誤差 , 踢掉
	 */
	private final static int KICK_PROCESSTIME_THRESHOLD  = ServerProperties.getInt(PropertyKey.precesstime_detect_kick_threshold, 10 * 60 ) * 1000;
	/**
	 * Processtime detect 週期, 過了後重新抓取client first process time (怕client~server之間因為系統誤差越來越大)
	 */
	private final static int PROCESSTIME_RESET_CYCLE  = ServerProperties.getInt(PropertyKey.precesstime_detect_cycle, 30 * 60 ) * 1000;
	/**
	 * server 多久檢查一次頻率狀態
	 */
	private final static int PERIOD_CHECK_LEGAL_PERIOD = 15 * 1000;
	/**
	 * 收到client第一個ProcessTime 紀錄
	 */
	private long clientFirstProcessTime = -1;
	/**
	 * 收到client第一個ProcessTime 時的Server SystemTime;
	 */
	private long clientFirstProcessServerTime = -1;
	
	/**
	 * client最後一次 sync時間
	 */
	private long clientLastSyncTime = System.currentTimeMillis();
	/**
	 * client最後一次 sync時間
	 */
	private long frquencySyncStartTime = -1;
	/**
	 * 累積收到統計封包次數
	 */
	private int statisCount = 0;
	/**
	 * 每次update 累積的period time , player. update 使用
	 */
	private float accumulateUpdateTime = PERIOD_CHECK_LEGAL_PERIOD;
		
	//private float pe
	public boolean addSyncRecord(long processTime){
			boolean result = true;
		long currentTime = System.currentTimeMillis();
		// 第一次, client的 processTime記下來
		if(ENABLE_PROCESSTIME_CHECK){
			// 記下 client 和 server 初始的時間
			if(clientFirstProcessTime < 0 || 
			   (clientFirstProcessTime > 0 && (currentTime - clientFirstProcessServerTime) > PROCESSTIME_RESET_CYCLE )){
				clientFirstProcessTime = processTime;
				clientFirstProcessServerTime = currentTime;
			}
			//檢察processTime 合不合法
			else{
				if(this.clientFirstProcessTime  > 0 && this.clientFirstProcessServerTime > 0){
					// 算出 client 和 server 從初始時間到現在經過的時間
					long clientPeriod  =  processTime - this.clientFirstProcessTime;
					long serverPeriod  =  currentTime - this.clientFirstProcessServerTime;
					
					// 兩邊經過時間差異太大就判定有問題
					if((clientPeriod - serverPeriod)  > KICK_PROCESSTIME_THRESHOLD){
						result = false;
					}
				}
			}
		}
		if(ENABLE_FREQUENCY_CHECK){
			if(frquencySyncStartTime < 0 ||
			   (frquencySyncStartTime > 0 && (currentTime - frquencySyncStartTime) > MAX_STATIC_PERIOD )){
				frquencySyncStartTime = currentTime - SYNC_CYCLE_STANDER;
				statisCount = 0;
			}
			statisCount++;
		}
		clientLastSyncTime = currentTime;
		return result;
	}

	/**
	 * 1min 檢查一次 玩家是否太久沒有發送sync封包, 
	 * @param diffTime
	 * @return
	 */
	public boolean update(long diffTime){
		boolean result = true;
		this.accumulateUpdateTime -= diffTime;
		if(accumulateUpdateTime < 0){
			result = checkSyncPeriodTimeOutLegal();
			if(result){
				result = checkFrequencyLegal();
			}
			accumulateUpdateTime = PERIOD_CHECK_LEGAL_PERIOD;
		}
		return result;
	}
	/**
	 * 檢查玩家離上一次sync 時間是否超過一定的threshold , 事的話則踢掉
	 * @return
	 */
	public boolean checkSyncPeriodTimeOutLegal(){
		if(!ENABLE_FREQUENCY_CHECK && !ENABLE_PROCESSTIME_CHECK)
			return true;
		long initTime = this.getClientLastSyncTime() <  0 ? 0 :  this.getClientLastSyncTime();
		return (Math.abs(System.currentTimeMillis() - initTime) < SYNC_TIMEOUT_PERIOD);
	}
	
	/**
	 * 檢查玩家同步頻率/ process time 是否正常
	 * 
	 * @return true 正常><br>
	 *         false 有問題
	 */
	private boolean checkFrequencyLegal(){
		boolean result = true;
		long currentTime =  System.currentTimeMillis();
		if(this.frquencySyncStartTime < 0 || 
		   (currentTime - this.frquencySyncStartTime) < MIN_BEGIN_KICK_STATIC_PERIOD){
			return true;
		}
		//標準頻率 ( per min times)
		float standerFrequency =  60000.0f / (SYNC_CYCLE_STANDER) ;
		float aveFrequency = (((float)this.statisCount) * 60000) / (float)((currentTime - this.frquencySyncStartTime) ); 
		//log.error("calc ave frequency:" + aveFrequency + " stander frequency:" + standerFrequency);
		if(aveFrequency > (standerFrequency + (standerFrequency * KICK_FRQUENCY_INACCURACY / 100))){
			log.error("kick by ilegal frequency");
			result = false;
		}		
		return result;
	}
	/**
	 * 通知client多久要發送一次sync資訊
	 * @param sessionID
	 */
	static public void sendPeriodSyncMessage(GUID sessionID) {
		int period = ((!PeriodSyncStatisInfo.ENABLE_FREQUENCY_CHECK) && (!PeriodSyncStatisInfo.ENABLE_PROCESSTIME_CHECK)) ? -1
				: PeriodSyncStatisInfo.SYNC_CYCLE_STANDER;
		
		MessageAction.sendMessage(new NetMessage(MessageTypes.S_PERIOD_SYNC_INFO, period, sessionID));
	}
	
	/**
	 * 回覆 client 送來的心跳消息要求
	 * 
	 * @param sessionID
	 *            client session id
	 * @param clientProcessTime
	 *            client 送來的時間
	 */
	static public void sendPeriodSyncResponse(GUID sessionID, long clientProcessTime) {
		if (sessionID == null)
			return;

		NetMessage msg = new NetMessage();
		msg.setSessionID(sessionID);
		msg.setMessageType(MessageTypes.S_PERIOD_SYNC_RESPONSE);
		msg.setMessageContent(clientProcessTime);
		MessageAction.sendMessage(msg);
	}

	public long getClientFirstProcessTime() {
		return clientFirstProcessTime;
	}

	public void setClientFirstProcessTime(long clientFirstProcessTime) {
		this.clientFirstProcessTime = clientFirstProcessTime;
	}

	public long getClientFirstProcessServerTime() {
		return clientFirstProcessServerTime;
	}

	public void setClientFirstProcessServerTime(long clientFirstProcessServerTime) {
		this.clientFirstProcessServerTime = clientFirstProcessServerTime;
	}

	public long getClientLastSyncTime() {
		return clientLastSyncTime;
	}

	public void setClientLastSyncTime(long clientLastSyncTime) {
		this.clientLastSyncTime = clientLastSyncTime;
	}

	public int getStatisCount() {
		return statisCount;
	}

	public void setStatisCount(int statisCount) {
		this.statisCount = statisCount;
	}
	public long getFrquencySyncStartTime() {
		return frquencySyncStartTime;
	}
	public void setFrquencySyncStartTime(long frquencySyncStartTime) {
		this.frquencySyncStartTime = frquencySyncStartTime;
	}

}
